package org.g4studio.core.mvc.xstruts.config;

import java.lang.reflect.InvocationTargetException;

/**
 * <p>
 * A JavaBean representing the configuration information of a
 * <code>&lt;forward&gt;</code> element from a Struts configuration file.
 * </p>
 * 
 * @version $Rev: 421119 $ $Date: 2005-08-14 17:24:39 -0400 (Sun, 14 Aug 2005) $
 * @since Struts 1.1
 */
public class ForwardConfig extends BaseConfig {
	// ------------------------------------------------------------- Properties

	/**
	 * The name of the ForwardConfig that this object should inherit properties
	 * from.
	 */
	protected String inherit = null;

	/**
	 * Have the inheritance values for this class been applied?
	 */
	protected boolean extensionProcessed = false;

	/**
	 * The unique identifier of this forward, which is used to reference it in
	 * <code>Action</code> classes.
	 */
	protected String name = null;

	/**
	 * <p>
	 * The URL to which this <code>ForwardConfig</code> entry points, which must
	 * start with a slash ("/") character. It is interpreted according to the
	 * following rules:
	 * </p>
	 * 
	 * <ul>
	 * 
	 * <li>If <code>contextRelative</code> property is <code>true</code>, the
	 * path is considered to be context-relative within the current web
	 * application (even if we are in a named module). It will be prefixed by
	 * the context path to create a server-relative URL.</li>
	 * 
	 * <li>If the <code>contextRelative</code> property is false, the path is
	 * considered to be the module-relative portion of the URL. It will be used
	 * as the replacement for the <code>$P</code> marker in the
	 * <code>forwardPattern</code> property defined on the
	 * {@link ControllerConfig} element for our current module. For the default
	 * <code>forwardPattern</code> value of <code>$C$M$P</code>, the resulting
	 * server-relative URL will be the concatenation of the context path, the
	 * module prefix, and the <code>path</code> from this
	 * <code>ForwardConfig</code>.
	 * 
	 * </li>
	 * 
	 * </ul>
	 */
	protected String path = null;

	/**
	 * <p>
	 * The prefix of the module to which this <code>ForwardConfig</code> entry
	 * points, which must start with a slash ("/") character.
	 * </p>
	 * <p>
	 * Usage note: If a forward config is used in a hyperlink, and a module is
	 * specified, the path must lead to another action and not directly to a
	 * page. This is in keeping with rule that in a modular application all
	 * links must be to an action rather than a page.
	 * </p>
	 */
	protected String module = null;

	/**
	 * Should a redirect be used to transfer control to the specified path?
	 */
	protected boolean redirect = false;

	/**
	 * <p>
	 * The name of a <code>commons-chain</code> command which should be looked
	 * up and executed before Struts dispatches control to the view represented
	 * by this config.
	 * </p>
	 */
	protected String command = null;

	/**
	 * <p>
	 * The name of a <code>commons-chain</code> catalog in which
	 * <code>command</code> should be looked up. If this value is undefined,
	 * then the command will be looked up in the "default" catalog. This value
	 * has no meaning except in the context of the <code>command</code>
	 * property.
	 * </p>
	 */
	protected String catalog = null;

	// ----------------------------------------------------------- Constructors

	/**
	 * Construct a new instance with default values.
	 */
	public ForwardConfig() {
		super();
	}

	/**
	 * Construct a new instance with the specified values.
	 * 
	 * @param name
	 *            Name of this forward
	 * @param path
	 *            Path to which control should be forwarded or redirected
	 * @param redirect
	 *            Should we do a redirect?
	 */
	public ForwardConfig(String name, String path, boolean redirect) {
		super();
		setName(name);
		setPath(path);
		setRedirect(redirect);
	}

	/**
	 * <p>
	 * Construct a new instance with the specified values.
	 * </p>
	 * 
	 * @param name
	 *            Name of this forward
	 * @param path
	 *            Path to which control should be forwarded or redirected
	 * @param redirect
	 *            Should we do a redirect?
	 * @param module
	 *            Module prefix, if any
	 */
	public ForwardConfig(String name, String path, boolean redirect, String module) {
		super();
		setName(name);
		setPath(path);
		setRedirect(redirect);
		setModule(module);
	}

	public String getExtends() {
		return (this.inherit);
	}

	public void setExtends(String inherit) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.inherit = inherit;
	}

	public boolean isExtensionProcessed() {
		return extensionProcessed;
	}

	public String getName() {
		return (this.name);
	}

	public void setName(String name) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.name = name;
	}

	public String getPath() {
		return (this.path);
	}

	public void setPath(String path) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.path = path;
	}

	public String getModule() {
		return (this.module);
	}

	public void setModule(String module) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.module = module;
	}

	public boolean getRedirect() {
		return (this.redirect);
	}

	public void setRedirect(boolean redirect) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.redirect = redirect;
	}

	public String getCommand() {
		return (this.command);
	}

	public void setCommand(String command) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.command = command;
	}

	public String getCatalog() {
		return (this.catalog);
	}

	public void setCatalog(String catalog) {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		this.catalog = catalog;
	}

	// ------------------------------------------------------ Protected Methods

	/**
	 * <p>
	 * Traces the hierarchy of this object to check if any of the ancestors are
	 * extending this instance.
	 * </p>
	 * 
	 * @param moduleConfig
	 *            The {@link ModuleConfig} that this config is from.
	 * @param actionConfig
	 *            The {@link ActionConfig} that this config is from, if
	 *            applicable. This parameter must be null if this forward config
	 *            is a global forward.
	 * @return true if circular inheritance was detected.
	 */
	protected boolean checkCircularInheritance(ModuleConfig moduleConfig, ActionConfig actionConfig) {
		String ancestorName = getExtends();

		if (ancestorName == null) {
			return false;
		}

		// Find our ancestor
		ForwardConfig ancestor = null;

		// First check the action config
		if (actionConfig != null) {
			ancestor = actionConfig.findForwardConfig(ancestorName);

			// If we found *this*, set ancestor to null to check for a global
			// def
			if (ancestor == this) {
				ancestor = null;
			}
		}

		// Then check the global forwards
		if (ancestor == null) {
			ancestor = moduleConfig.findForwardConfig(ancestorName);

			if (ancestor != null) {
				// If the ancestor is a global forward, set actionConfig
				// to null so further searches are only done among
				// global forwards.
				actionConfig = null;
			}
		}

		while (ancestor != null) {
			// Check if an ancestor is extending *this*
			if (ancestor == this) {
				return true;
			}

			// Get our ancestor's ancestor
			ancestorName = ancestor.getExtends();

			// check against ancestors extending same named ancestors
			if (ancestor.getName().equals(ancestorName)) {
				// If the ancestor is extending a config with the same name
				// make sure we look for its ancestor in the global forwards.
				// If we're already at that level, we return false.
				if (actionConfig == null) {
					return false;
				} else {
					// Set actionConfig = null to force us to look for global
					// forwards
					actionConfig = null;
				}
			}

			ancestor = null;

			// First check the action config
			if (actionConfig != null) {
				ancestor = actionConfig.findForwardConfig(ancestorName);
			}

			// Then check the global forwards
			if (ancestor == null) {
				ancestor = moduleConfig.findForwardConfig(ancestorName);

				if (ancestor != null) {
					// Limit further checks to moduleConfig.
					actionConfig = null;
				}
			}
		}

		return false;
	}

	// --------------------------------------------------------- Public Methods

	/**
	 * <p>
	 * Inherit values that have not been overridden from the provided config
	 * object. Subclasses overriding this method should verify that the given
	 * parameter is of a class that contains a property it is trying to inherit:
	 * </p>
	 * 
	 * <pre>
	 * if (config instanceof MyCustomConfig) {
	 * 	MyCustomConfig myConfig = (MyCustomConfig) config;
	 * 
	 * 	if (getMyCustomProp() == null) {
	 * 		setMyCustomProp(myConfig.getMyCustomProp());
	 * 	}
	 * }
	 * </pre>
	 * 
	 * <p>
	 * If the given <code>config</code> is extending another object, those
	 * extensions should be resolved before it's used as a parameter to this
	 * method.
	 * </p>
	 * 
	 * @param config
	 *            The object that this instance will be inheriting its values
	 *            from.
	 * @see #processExtends(ModuleConfig, ActionConfig)
	 */
	public void inheritFrom(ForwardConfig config) throws ClassNotFoundException, IllegalAccessException,
			InstantiationException, InvocationTargetException {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		// Inherit values that have not been overridden
		if (getCatalog() == null) {
			setCatalog(config.getCatalog());
		}

		if (getCommand() == null) {
			setCommand(config.getCommand());
		}

		if (getModule() == null) {
			setModule(config.getModule());
		}

		if (getName() == null) {
			setName(config.getName());
		}

		if (getPath() == null) {
			setPath(config.getPath());
		}

		if (!getRedirect()) {
			setRedirect(config.getRedirect());
		}

		inheritProperties(config);
	}

	/**
	 * <p>
	 * Inherit configuration information from the ForwardConfig that this
	 * instance is extending. This method verifies that any forward config
	 * object that it inherits from has also had its processExtends() method
	 * called.
	 * </p>
	 * 
	 * @param moduleConfig
	 *            The {@link ModuleConfig} that this config is from.
	 * @param actionConfig
	 *            The {@link ActionConfig} that this config is from, if
	 *            applicable. This must be null for global forwards.
	 * @see #inheritFrom(ForwardConfig)
	 */
	public void processExtends(ModuleConfig moduleConfig, ActionConfig actionConfig) throws ClassNotFoundException,
			IllegalAccessException, InstantiationException, InvocationTargetException {
		if (configured) {
			throw new IllegalStateException("Configuration is frozen");
		}

		String ancestorName = getExtends();

		if ((!extensionProcessed) && (ancestorName != null)) {
			ForwardConfig baseConfig = null;

			// We only check the action config if we're not a global forward
			boolean checkActionConfig = (this != moduleConfig.findForwardConfig(getName()));

			// ... and the action config was provided
			checkActionConfig &= (actionConfig != null);

			// ... and we're not extending a config with the same name
			// (because if we are, that means we're an action-level forward
			// extending a global forward).
			checkActionConfig &= !ancestorName.equals(getName());

			// We first check in the action config's forwards
			if (checkActionConfig) {
				baseConfig = actionConfig.findForwardConfig(ancestorName);
			}

			// Then check the global forwards
			if (baseConfig == null) {
				baseConfig = moduleConfig.findForwardConfig(ancestorName);
			}

			if (baseConfig == null) {
				throw new NullPointerException("Unable to find " + "forward '" + ancestorName + "' to extend.");
			}

			// Check for circular inheritance and make sure the base config's
			// own extends have been processed already
			if (checkCircularInheritance(moduleConfig, actionConfig)) {
				throw new IllegalArgumentException("Circular inheritance detected for forward " + getName());
			}

			if (!baseConfig.isExtensionProcessed()) {
				baseConfig.processExtends(moduleConfig, actionConfig);
			}

			// copy values from the base config
			inheritFrom(baseConfig);
		}

		extensionProcessed = true;
	}

	/**
	 * Return a String representation of this object.
	 */
	public String toString() {
		StringBuffer sb = new StringBuffer("ForwardConfig[");

		sb.append("name=");
		sb.append(this.name);
		sb.append(",path=");
		sb.append(this.path);
		sb.append(",redirect=");
		sb.append(this.redirect);
		sb.append(",module=");
		sb.append(this.module);
		sb.append(",extends=");
		sb.append(this.inherit);
		sb.append(",catalog=");
		sb.append(this.catalog);
		sb.append(",command=");
		sb.append(this.command);
		sb.append("]");

		return (sb.toString());
	}
}
